#ifndef OBJECTQUEUE_H
#define OBJECTQUEUE_H
#include <windows.h>
#include <mutex>




template<class TData>
class ObjectQueue{
  public:
    ObjectQueue(int ItemNum);
    ~ObjectQueue();

    void Write(TData* Data);
    TData* Read();
    int TryWrite(TData* Data);
    TData* TryRead();

  private:
    TData** Items;
    int ItemNum;
    int I;
    int O;
    
    void* Lock;
    void* InEvent;
    void* OutEvent;
};




template<class TData>
ObjectQueue<TData>::ObjectQueue(int ItemNum){

  this->Lock=new std::mutex();

  this->ItemNum=ItemNum;
  this->Items=new TData*[ItemNum];

  this->InEvent=CreateEvent(nullptr,false,false,nullptr);
  this->OutEvent=CreateEvent(nullptr,false,false,nullptr);

  this->I=0;
  this->O=0;
}

template<class TData>
ObjectQueue<TData>::~ObjectQueue(){

  CloseHandle((HANDLE)InEvent);
  CloseHandle((HANDLE)OutEvent);

  delete (std::mutex*)Lock;

  delete[] Items;

}

template<class TData>
void ObjectQueue<TData>::Write(TData* Data){
  
  while(0!=TryWrite(Data)){
    WaitForSingleObject((HANDLE)OutEvent,INFINITE);
  }
}

template<class TData>
TData* ObjectQueue<TData>::Read(){
  TData* r;
  
  while(nullptr==(r=TryRead())){
    WaitForSingleObject((HANDLE)InEvent,INFINITE);
  }

  return r;
}

template<class TData>
int ObjectQueue<TData>::TryWrite(TData* Data){
  std::mutex* mutex;
  int I;
  int EmptyLength;

  mutex=(std::mutex*)Lock;

  mutex->lock();

  I=this->I;
  const int O=this->O;

  if(I<O){
    EmptyLength=O-I;
  }else{
    EmptyLength=ItemNum-I+O;
  }

  if(EmptyLength>1){

    Items[I]=Data;
    I++;
    if(I==ItemNum){
      I=0;
    }

    this->I=I;
    
    mutex->unlock();
    SetEvent((HANDLE)InEvent);
    return 0;
  }else{
    mutex->unlock();
    return -1;
  }

}

template<class TData>
TData* ObjectQueue<TData>::TryRead(){
  TData* r;
  std::mutex* mutex;
  int O;
  int UsedLength;

  mutex=(std::mutex*)Lock;

  mutex->lock();

  const int I=this->I;
  O=this->O;

  if(I<O){
    UsedLength=ItemNum+O-I;
  }else{
    UsedLength=I-O;
  }

  if(UsedLength<1){
    mutex->unlock();
    return nullptr;
  }

  r=Items[O];
  O++;
  if(O==ItemNum){
    O=0;
  }
  
  this->O=O;

  mutex->unlock();
  SetEvent((HANDLE)OutEvent);
  return r;
}




#endif // OBJECTQUEUE_H
